package crontab

import (
	"errors"
	"github.com/gogf/gf/os/glog"
	"github.com/gogf/gf/util/gconv"
	"snake/library/base"
	"snake/app/model/crontab"
	"snake/app/model/ops"
	//"github.com/robfig/cron"
	"github.com/jakecoffman/cron"
	"snake/app/service/script"
	"snake/app/service/host"
	"snake/app/utils"
	"github.com/ouqiang/goutil"
	"sync"
	"strconv"
	"strings"
	"k8s.io/apimachinery/pkg/util/uuid"
)

// 请求参数
type Request struct {
	*crontab.Crontab
	UserId int `form:"userId" json:"userId"`
}

var (
	ServiceTask Task
)

var (
	// 定时任务调度管理器
	serviceCron *cron.Cron

	// 同一任务是否有实例处于运行中
	runInstance Instance

	// 任务计数-正在运行的任务
	taskCount TaskCount

	// 并发队列, 限制同时运行的任务数量
	concurrencyQueue ConcurrencyQueue
)

// 并发队列
type ConcurrencyQueue struct {
	queue chan struct{}
}

func (cq *ConcurrencyQueue) Add() {
	cq.queue <- struct{}{}
}

func (cq *ConcurrencyQueue) Done() {
	<-cq.queue
}

// 任务计数
type TaskCount struct {
	wg   sync.WaitGroup
	exit chan struct{}
}

func (tc *TaskCount) Add() {
	tc.wg.Add(1)
}

func (tc *TaskCount) Done() {
	tc.wg.Done()
}

func (tc *TaskCount) Exit() {
	tc.wg.Done()
	<-tc.exit
}

func (tc *TaskCount) Wait() {
	tc.Add()
	tc.wg.Wait()
	close(tc.exit)
}

// 任务ID作为Key
type Instance struct {
	m sync.Map
}

// 是否有任务处于运行中
func (i *Instance) has(key int) bool {
	_, ok := i.m.Load(key)
	return ok
}

func (i *Instance) add(key int) {
	i.m.Store(key, struct{}{})
}

func (i *Instance) done(key int) {
	i.m.Delete(key)
}

type Task struct{}

//--------------------CRUD-----------------------------
// 通过id获取实体
func GetById(id int64) (*crontab.Crontab, error) {
	if id <= 0 {
		glog.Error(" get id error")
		return new(crontab.Crontab), errors.New("参数不合法")
	}
	return crontab.Model.FindOne(" id = ?", id)
}

// 根据条件获取实体
func GetOne(form *base.BaseForm) (*crontab.Crontab, error) {
	where := " 1 = 1 "
	var params []interface{}
	if form.Params != nil && form.Params["id"] != "" {
		where += " and id = ? "
		params = append(params, gconv.Int(form.Params["id"]))
	}
	if form.Params != nil && form.Params["uuid"] != "" {
		where += " and uuid = ? "
		params = append(params, gconv.Int(form.Params["uuid"]))
	}
	return crontab.Model.FindOne(where, params)
}

// 删除实体
func Delete(id int64, userId int) (int64, error) {
	if id <= 0 {
		glog.Error("delete id error")
		return 0, errors.New("参数不合法")
	}
	r, err1 := crontab.Model.Delete(" id = ?", id)
	if err1 != nil {
		return 0, err1
	}
	//更新crontab
	glog.Info("删除定时执行任务")
	ServiceTask.Remove(int(id))
	return r.RowsAffected()
}

// 保存实体
func Save(request *Request) (int64, error) {
	entity := (*crontab.Crontab)(nil)
	err := gconv.Struct(request.Crontab, &entity)
	if err != nil {
		return 0, errors.New("数据错误")
	}
	// 判断新增还是修改
	if entity.Id <= 0 {
		// glog.Info("新增数据:", entity)
		r, err := crontab.Model.Insert(entity)
		if err != nil {
			glog.Error("新增数据失败:", err)
			return 0, err
		}
		// 回写主键
		lastId, err := r.LastInsertId()
		if err != nil {
			glog.Error(err)
			return 0, err
		}
		entity.Id = gconv.Int(lastId)
		//更新crontab
		glog.Info("添加新的定时执行任务")
		ServiceTask.Add(*entity)
		return r.RowsAffected()
	} else {
		// glog.Debug("更新数据:", entity)
		r, err := crontab.Model.OmitEmpty().Where(" id = ?", entity.Id).Update(entity)
		if err != nil {
			glog.Error(err)
			return 0, err
		}
		//更新crontab
		glog.Info("更新定时任务执行")
		ServiceTask.RemoveAndAdd(*entity)
		return r.RowsAffected()
	}
}

// 列表数据查询
func List(form *base.BaseForm) ([]*crontab.Crontab, error) {
	where := " 1 = 1 "
	var params []interface{}
	return crontab.Model.Order(form.OrderBy).FindAll(where, params)
}


// 分页查询
func Page(form *base.BaseForm) ([]crontab.Crontab, error) {
	if form.Page <= 0 || form.Rows <= 0 {
		glog.Error("page param error", form.Page, form.Rows)
		return []crontab.Crontab{}, nil
	}
	where := " 1 = 1 "
	var params []interface{}
	if form.Params != nil && form.Params["name"] != "" {
		where += " and name like ? "
		params = append(params, "%"+form.Params["name"]+"%")
	}
	num, err := crontab.Model.As("t").FindCount(where, params)
	form.TotalSize = num
	form.TotalPage = num / form.Rows
	if err != nil {
		glog.Error("page count error", err)
		return []crontab.Crontab{}, err
	}

	// 没有数据直接返回
	if num == 0 {
		form.TotalPage = 0
		form.TotalSize = 0
		return []crontab.Crontab{}, err
	}

	var resData []crontab.Crontab
	dbModel := crontab.Model.As("t").Fields(crontab.Model.Columns() + ",script.name as scriptName, su.real_name as updateName")
	dbModel = dbModel.LeftJoin("sys_user su", " t.update_id = su.id ")
	dbModel = dbModel.LeftJoin("script", " t.script_id = script.id ")
	err = dbModel.Where(where, params).Order(form.OrderBy).Page(form.Page, form.Rows).M.Structs(&resData)
	if err != nil {
		glog.Error("page list error", err)
		return []crontab.Crontab{}, err
	}

	return resData, nil
}


//-----------------------业务相关-----------------------------

// 初始化定时任务
func (task Task) Initialize(){
	//初始化定时任务管理器
	serviceCron = cron.New()
	serviceCron.Start()
	//初始化并发队列
	concurrencyQueue = ConcurrencyQueue{queue: make(chan struct{}, 10)}
	//init taskcount
	taskCount = TaskCount{sync.WaitGroup{}, make(chan struct{})}
	go taskCount.Wait()
	//定时任务数
	taskNum := 0
	//获取crontab列表
	// form := make(map[string]interface{},0)
	form := base.NewForm(make(map[string]interface{},0))
	crontabList,err := List(&form)
	if err != nil {
		glog.Error("获取定时任务列表失败,", err.Error())
		return
	}
	//初始化crontab任务
	glog.Info("初始化定时任务...")
	for _, c := range crontabList {
		taskNum++
		task.Add(*c)
	}
}

// 批量添加任务
func (task Task) BatchAdd(cs []crontab.Crontab) {
	for _, item := range cs {
		task.RemoveAndAdd(item)
	}
}

// 删除任务后添加
func (task Task) RemoveAndAdd(c crontab.Crontab) {
	task.Remove(c.Id)
	task.Add(c)
}

// 添加任务
func (task Task) Add(c crontab.Crontab) {
	taskFunc := createJob(c)
	if taskFunc == nil {
		glog.Error("创建任务处理Job失败")
		return
	}
	cronName := strconv.Itoa(c.Id)
	err := goutil.PanicToError(func() {
		serviceCron.AddFunc(c.Spec, taskFunc, cronName)
	})
	if err != nil {
		glog.Error("添加任务到调度器失败#", err)
	}
}

func (task Task) Remove(id int) {
	serviceCron.RemoveJob(strconv.Itoa(id))
}

// 直接运行任务
func (task Task) Run(c crontab.Crontab) {
	go createJob(c)()
}

func createJob(c crontab.Crontab) cron.FuncJob {
	taskFunc := func() {
		taskCount.Add()
		defer taskCount.Done()		
		concurrencyQueue.Add()
		defer concurrencyQueue.Done()
		//创建Job
		job := ops.Job{}
		job.ScriptId = c.ScriptId
		job.JobId = gconv.String(uuid.NewUUID())
		job.Tasks = make([]ops.Task,0)
		job.Status = "created"
		//执行的主机列表
		hostIdList := strings.Split(c.Hosts,",")
		for k,v := range hostIdList {
			h, err := host.GetById(gconv.Int64(v))
			if err != nil {
				glog.Error("获取主机失败: id=", v)
				glog.Error(err.Error())
				continue
			}
			mhost, err := utils.StructToMap(h, "json")
			if err != nil {
				glog.Error("struct 转 map失败 ", err.Error())
				continue
			}
			//子任务task
			task := ops.Task{}
			task.Id = gconv.Int(k)
			task.ScriptId = job.ScriptId
			task.Status = "created"
			task.Host = mhost

			task.Log = ""
			job.Tasks = append(job.Tasks, task)
		}
		// respData := make(map[string]string, 0)
		// respData["jobId"] = job.JobId
		//创建任务后返回任务ID, 再往下执行任务
		glog.Info("run crontab job:", job)
		go script.RunJob(&job)
	}
	return taskFunc
}